const {
    generateRequest,
    generateResponse,
    generateBrowserContext,
    generateRequestCallback,
    conversionBlobToString,
    PongProcessName, PongProcessTime, RequestDefaultTimeout
} = require("../common");

const createBrowser = (url, name) => {
    return {
        name: name,
        url: url,    // 连接地址
        websocket: null,    // websocket 实例
        pongTaskTime: PongProcessTime, // 心跳时间（毫秒）
        requestTimeout: RequestDefaultTimeout,  // 发送request超时时间
        isShowPongLogs: false,   // 心跳包任务日志
        isConnStatus: false,   // 当前连接状态
        pongTaskTicker: null, // 心跳包发送任务
        requestCommonHeader: {},    // request common header
        responseCommonHeader: {},    // response common header
        requestProcessMap: {},  // 保存 server 对 client 发送 request 时的 process 处理器
        requestCallbackMap: {},    // 保存 client 发送消息到 server 时的回调处理器
        serverRequestFilters: [],    // 被动接收request时的过滤器（别人给自己发送 fun: handlerRequest）
        serverResponseFilters: [],   // 被动响应response时的过滤器（自己给别人响应 fun: handlerRequest）
        clientRequestFilters: [],    // 主动发送request时的过滤器（fun: sendRequest）
        clientResponseFilters: [],   // 主动接收response时的过滤器（fun: handlerResponse）
        writeBeforeFunc: function (data) {
            return data // 返回必须时 string
        },
        readBeforeFunc: function (data) {
            return data // 返回必须时 string
        },
        defaultProcessFunc: function (context) {
            console.log('没有对应的 process 处理函数', context)
        },
        getWebsocketConnection: function (url) {
            return new Promise((resolve, reject) => {
                const websocket = new WebSocket(url)
                websocket.onerror = (event) => {
                    console.error('getWebsocketConnection: onerror', event)
                    this.isConnStatus = false
                    reject(event)
                }
                websocket.onopen = () => {
                    console.log('getWebsocketConnection: onopen', '连接成功')
                    this.isConnStatus = true
                    resolve(websocket)
                }
            })
        },
        openConnection: function () {
            return new Promise(async (resolve, reject) => {
                const ws = await this.getWebsocketConnection(this.url)
                if (!this.isConnStatus) {
                    reject(ws)
                } else {
                    this.websocket = ws
                    this.websocket.onclose = (event) => {
                        console.log('openConnection: onclose', event)
                    }
                    this.websocket.onmessage = (event) => {
                        if (event.data.size !== undefined && typeof event.data === "object") {
                            this.handlerResponse(event.data)  // 响应消息走字节通道
                        } else {
                            this.handlerRequest(event.data)    // 主动请求消息走字符串通道
                        }
                    }
                    this.openPongTask()
                    resolve(ws)
                }
            })
        },
        handlerRequest: function (data) {

            // 解压处理在这里加入
            const msg = this.readBeforeFunc(data)

            // 校验 request 数据
            const msgObj = JSON.parse(msg)
            if (!msgObj.U || !msgObj.P || !msgObj.H || !msgObj.B) {
                console.error('handlerRequest: 服务端发送的数据错误', msg)
                return;
            }

            // 创建 request
            const request = generateRequest(msgObj.U)
            request.P = msgObj.P
            request.H = msgObj.H
            request.B = msgObj.B

            // 创建 response
            const response = generateResponse(msgObj.U)
            response.H = {...this.responseCommonHeader}

            // 创建 context
            const context = generateBrowserContext()
            context.client = this   // browser
            context.request = request
            context.response = response

            // 在这里调用request过滤器
            for (const filterFunction of this.serverRequestFilters) {
                if (filterFunction(context)) return
            }

            if (request.process && request.process.length > 0) {
                const func = this.requestProcessMap[request.process]
                if (func != null) {
                    func(context) // 执行目标函数函数执行处理
                } else {
                    this.defaultProcessFunc(context)    // 调用默认process处理
                }
            }

            // 在这里调用response过滤器
            for (const filterFunction of this.serverResponseFilters) {
                if (filterFunction(context)) return
            }

            // 压缩处理在这里加入
            const res = this.writeBeforeFunc(JSON.stringify(response))

            this.writeResponseMessage(res)
        },
        handlerResponse: async function (data) {

            // 解压处理在这里加入
            const msg = this.readBeforeFunc(data)

            // 校验 response
            const msgObj = JSON.parse(msg)
            if (!msgObj.U || !msgObj.C || !msgObj.M || !msgObj.H || !msgObj.B) {
                console.error('handlerResponse: json转为response错误', msg)
                return
            }

            const rc = this.requestCallbackMap[msgObj.U]
            if (rc == null) {
                console.log("handlerResponse: 没有对应的 callback 处理", msg)
                return
            }

            delete this.requestCallbackMap[msgObj.U]

            // 还原 response
            rc.context.response.response.C = msgObj.C
            rc.context.response.response.M = msgObj.M
            rc.context.response.response.H = msgObj.H
            rc.context.response.response.B = msgObj.B

            rc.callback(rc.context)

            // 在这里调用response过滤器
            for (const filterFunction of this.clientResponseFilters) {
                if (filterFunction(rc.context)) return
            }
        },
        sendRequest: function (request) {
            return new Promise(async (resolve, reject) => {
                // 合并 common 到 header
                request.H = {...this.requestCommonHeader, ...request.H}

                // 创建 response
                const response = generateResponse(request.U)

                // 创建 context
                const context = generateBrowserContext()
                context.client = this
                context.request = request
                context.response = response

                // 创建 timeout
                const timeoutTask = setTimeout(() => {
                    reject('timeout')
                }, this.requestTimeout)

                // 创建 callback 保存回调函数等待 handlerResponse 回调
                const rc = generateRequestCallback()
                rc.context = context
                rc.callback = (browserContext) => {
                    clearTimeout(timeoutTask)
                    resolve(browserContext)
                }
                this.requestCallbackMap[request.U] = rc

                // 应用request过滤器处理
                for (const filterFunction of this.clientRequestFilters) {
                    if (filterFunction(context)) return
                }

                // 压缩处理在这里加入
                const data = this.writeBeforeFunc(JSON.stringify(request))

                await this.writeRequestMessage(data)
            })
        },
        writeRequestMessage: async function (data) {
            if (typeof data === 'string') {
                this.websocket.send(data)
            } else if (typeof data === 'object') {
                this.websocket.send(JSON.stringify(data))
            } else {
                this.websocket.send(await conversionBlobToString(data))
            }
        },
        writeResponseMessage: function (data) {
            if (typeof data === 'string') {
                this.websocket.send(new Blob([data]))
            } else if (typeof data === 'object') {
                this.websocket.send(new Blob([JSON.stringify(data)]))
            } else {
                this.websocket.send(data)
            }
        },
        openPongTask: function () {
            this.pongTaskTicker = setInterval(() => {
                if (this.websocket == null) {
                    return
                }
                const req = generateRequest()
                req.P = PongProcessName
                req.B = new Date().getTime().toString(10)
                this.sendRequest(req)
                    .then(context => {
                        if (this.isShowPongLogs) {
                            console.log("openPongTask: 心跳任务消息正常", context.response.C)
                        }
                    })
                    .catch(err => {
                        this.openConnection(this.url, this.name).then()   // 重连
                    })
            }, this.pongTaskTime)
        },
        CloseConnection: function (code, msg) {
            if (this.pongTaskTicker) {
                clearInterval(this.pongTaskTicker)
            }
            this.pongTaskTicker = null
            this.websocket.close(code, msg)
            this.websocket = null
        },
        SetRequestCommonHeader: function (header = {}) {
            this.requestCommonHeader = header
        },
        SetResponseCommonHeader: function (header = {}) {
            this.responseCommonHeader = header
        },
        AddServerRequestFilters: function (filter) {
            this.serverRequestFilters.push(filter)
        },
        AddServerResponseFilters: function (filter) {
            this.serverResponseFilters.push(filter)
        },
        AddClientRequestFilters: function (filter) {
            this.clientRequestFilters.push(filter)
        },
        AddClientResponseFilters: function (filter) {
            this.clientResponseFilters.push(filter)
        },
        RegisterProcessFunc: function (name, process) {
            this.requestProcessMap[name] = process
        },
        ShowPongLogs: function (isShowPongLogs) {
            this.isShowPongLogs = isShowPongLogs
        },
        SetWriteBeforeFunc: function (func) {
            this.writeBeforeFunc = func
        },
        SetReadBeforeFunc: function (func) {
            this.readBeforeFunc = func
        },
        SetDefaultProcessFunc: function (func) {
            this.defaultProcessFunc = func
        },
        SendMessage: function (process, header, body) {
            const request = generateRequest()
            request.P = process
            request.H = header
            request.B = body
            return this.sendRequest(request)
        }
    }
}

module.exports = async (url, browserName) => {
    if (typeof window !== 'object') {
        throw new Error('websockethttp browser 只能在浏览器环境使用')
    }
    const browser = createBrowser(url, browserName)
    await browser.openConnection()
    return browser.isConnStatus ? browser : null
}
